package com.youlai.boot.system.service.impl;

import cn.hutool.core.io.NullOutputStream;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import com.alibaba.excel.util.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.youlai.boot.system.mapper.BackupRecordMapper;
import com.youlai.boot.system.model.entity.BackupRecord;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.PumpStreamHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.io.*;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

@Slf4j
@Service
public class BackupService {
    @Autowired
    private BackupRecordMapper backupRecordMapper;

    @Value("${bakTask.backupDir}")
    private String backupDir;

    @Value("${bakTask.mysqlPathPrefix}")
    private String mysqlPathPrefix;

    @Value("${spring.datasource.username}")
    private String username;

    @Value("${spring.datasource.password}")
    private String password;

    @Value("${spring.datasource.url}")
    private String url;
@Scheduled(cron = "0 0 3 ? * WED")
    public void week(){
        performBackup(true);
    }


    /**
     * 备份数据库信息
     * @param isScheduled 是否为定时任务
     * @throws IOException
     * @throws InterruptedException
     */
    public void performBackup(boolean isScheduled){
        // 创建备份文件路径
        long snowflakeNextId = IdUtil.getSnowflakeNextId();
        String fileName = snowflakeNextId + ".sql";
        String fullpath = backupDir + fileName;
        String databaseName = extractDatabaseName(url);
        if(StringUtils.isBlank(databaseName)){
            log.error("获取数据库名称失败"+databaseName);
            log.info("任务结束");
            return;
        }

        // 准备mysqldump命令
        CommandLine cmdLine = new CommandLine(mysqlPathPrefix+"mysqldump");
        cmdLine.addArgument("--user="+username); // 使用你的数据库用户名替换root
        cmdLine.addArgument("--password="+password); // 直接在这里指定密码，或者更安全的做法是使用环境变量或配置文件读取密码
        cmdLine.addArgument(databaseName); // 你的数据库名
        try {
            // 设置输出到文件
            File outFile = new File(fullpath);
            FileOutputStream outputStream = new FileOutputStream(outFile);

            // 使用PumpStreamHandler将命令的标准输出重定向到文件
            PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream,new NullOutputStream());

            DefaultExecutor executor = new DefaultExecutor();
            executor.setStreamHandler(streamHandler);

            try {
                // 执行命令
                int exitCode = executor.execute(cmdLine);
                System.out.println("Backup process exited with code: " + exitCode);
            } finally {
                outputStream.close(); // 确保关闭输出流
            }
            BackupRecord record = new BackupRecord();
            record.setFilePath(fullpath);
            backupRecordMapper.insert(record);

                cleanupOldBackups();

        }catch (Exception e){
            log.error("任务执行失败{}，{}",isScheduled,e.getMessage());
            e.printStackTrace();
        }
    }


    private void  cleanupOldBackups() throws IOException {
        QueryWrapper<BackupRecord> queryWrapper = new QueryWrapper<>();
        List<BackupRecord> records = backupRecordMapper.selectList(queryWrapper);
        if (records.size() > 10) {  // 如果备份记录数超过10
            BackupRecord oldest = Collections.min(records, Comparator.comparing(BackupRecord::getBackupTime));

            Path filePath = Paths.get(oldest.getFilePath());

            // 检查文件是否存在并且是一个标准文件（非目录）
            if (Files.exists(filePath) && Files.isRegularFile(filePath)) {
                try {
                    // 删除文件
                    Files.deleteIfExists(filePath);

                    // 从数据库中删除对应的记录
                    backupRecordMapper.deleteById(oldest.getId());

                    log.info("Deleted backup file and corresponding record: " + oldest.getFilePath());
                } catch (Exception e) {
                    // 记录错误日志
                    log.error("Error deleting backup file or record: " + e.getMessage());
                }
            } else {
                // 如果文件不存在或不是标准文件，仅从数据库中删除对应的记录
                try {
                    backupRecordMapper.deleteById(oldest.getId());
                    log.info("No file found for deletion, only deleted the record from database: " + oldest.getId());
                } catch (Exception e) {
                    // 记录错误日志
                    log.error("Error deleting record from database: " + e.getMessage());
                }
            }
        }
    }
    public List<BackupRecord> listBackups(){

      return   backupRecordMapper.selectList(null);
    }


    /**
     * 恢复数据库文件
     * @param id 备份数据的id
     * @return 执行结果
     * @throws Exception
     */
    public String restoreDatabase(String id) throws Exception {
        BackupRecord record = backupRecordMapper.selectById(id);
        Assert.notNull(record,"未找到备份数据");
        String sqlFilePath = record.getFilePath();
        String databaseName = extractDatabaseName(url);
        if(StringUtils.isBlank(databaseName)){
            log.error("获取数据库名称失败"+databaseName);
            log.info("任务结束");
            return null;
        }
        // 准备mysql命令
        CommandLine cmdLine = new CommandLine(mysqlPathPrefix+"mysql");
        cmdLine.addArgument("--user=" + username);
        cmdLine.addArgument("--password=" + password);
        cmdLine.addArgument(databaseName);

        File file = new File(sqlFilePath);
        boolean exists = file.exists();
        Assert.isTrue(exists,"备份文件不存在，请联系技术人员");
        // 打开SQL文件作为输入流
        FileInputStream inputStream = new FileInputStream(file);

        // 使用PumpStreamHandler来处理输入和输出流
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream, outputStream, inputStream);

        DefaultExecutor executor = new DefaultExecutor();
        executor.setStreamHandler(streamHandler);

        try {
            // 执行命令
            int exitCode = executor.execute(cmdLine);
            System.out.println("Restore process exited with code: " + exitCode);
            // 获取输出并转换为字符串
            String outputString = outputStream.toString();
            return outputString;
        } finally {
            inputStream.close(); // 确保关闭输入流
            outputStream.close(); // 确保关闭ByteArrayOutputStream
        }
    }


    /**
     * 根据jdbcurl来获取到当前数据库的名称
     * @param jdbcUrl 数据库连接url
     * @return 连接中的数据库名称
     */
    private String extractDatabaseName(String jdbcUrl) {

        try {
            // 去掉 "jdbc:mysql://" 前缀
            String withoutPrefix = jdbcUrl.replaceFirst("^jdbc:mysql://", "");

            // 分割剩余部分，以斜杠作为分隔符
            String[] parts = withoutPrefix.split("/", 2);
            if (parts.length < 2) {
                return null; // 如果没有找到斜杠，则返回null表示无法解析
            }

            // 获取分割后的第二部分，并以问号为界截断参数列表
            String databaseAndParams = parts[1];
            int queryIndex = databaseAndParams.indexOf('?');
            if (queryIndex != -1) {
                return databaseAndParams.substring(0, queryIndex); // 截取数据库名
            } else {
                return databaseAndParams; // 没有参数的情况直接返回
            }
        }catch (Exception e){
            return null;
        }
    }
}
